/*
 * MIT License
 * 
 * Copyright (c) 2024-2025 milkpotatoes
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <cstring>

#include "utils/file.h"
#include "uv.h"

#include "module_manager/native_module_manager.h"

NativeModuleManager *NativeModuleManager::instance_ = nullptr;

static std::mutex g_modulesMutex;
static std::map<std::string, std::pair<uv_lib_t *, NativeModule *>> g_nativeModules;

static std::mutex g_lastModuleMutex;
static NativeModule *g_lastModule = nullptr;

NativeModuleManager::NativeModuleManager() {}

NativeModuleManager::~NativeModuleManager()
{
    std::lock_guard<std::mutex> lock(g_modulesMutex);
    for (auto [_, module] : g_nativeModules)
    {
        uv_dlclose(module.first);
        delete module.first;
        delete module.second;
    }
    g_nativeModules.clear();
}

NativeModuleManager *NativeModuleManager::GetInstance()
{
    static NativeModuleManager instance;
    return &instance;
}

bool NativeModuleManager::CreateHeadNativeModule()
{
    return true;
}

bool NativeModuleManager::CreateTailNativeModule()
{
    return true;
}
#if !defined(WINDOWS_PLATFORM) && !defined(MAC_PLATFORM) && !defined(__BIONIC__) && !defined(IOS_PLATFORM) && \
    !defined(LINUX_PLATFORM)
void NativeModuleManager::CreateSharedLibsSonames()
{
}
#endif

void NativeModuleManager::CreateLdNamespace(const std::string moduleName, const char *lib_ld_path,
                                            [[maybe_unused]] const bool &isSystemApp) {}
void NativeModuleManager::SetAppLibPath(const std::string &moduleName, const std::vector<std::string> &appLibPath,
                                        const bool &isSystemApp) {}

void NativeModuleManager::MoveApiAllowListCheckerPtr(
    std::unique_ptr<ApiAllowListChecker> &apiAllowListChecker, NativeModule *nativeModule) {}

// must lock g_lastModuleMutex to keep only one module is loading.
void NativeModuleManager::Register(NativeModule *nativeModule)
{
    if (g_lastModule)
        return;

    g_lastModule = new NativeModule;
    if (g_lastModule == nullptr)
        abort();

    g_lastModule->name = nativeModule->name;
    g_lastModule->moduleName = nativeModule->moduleName;
    g_lastModule->fileName = nativeModule->fileName;
    g_lastModule->registerCallback = nativeModule->registerCallback;
    g_lastModule->version = nativeModule->version;
    g_lastModule->flags = nativeModule->flags;
}

NativeModule *NativeModuleManager::FindNativeModuleByCache(const char *moduleName,
                                                           char nativeModulePath[][NAPI_PATH_MAX],
                                                           NativeModule *&cacheNativeModule,
                                                           NativeModuleHeadTailStruct &cacheHeadTailStruct)
{
    std::string path(moduleName);
    auto found = g_nativeModules.find(path);
    if (found != g_nativeModules.end())
        return found->second.second;
    return nullptr;
}

NativeModule *NativeModuleManager::LoadNativeModule(const char *moduleName, const char *path, bool isAppModule,
                                                    std::string &errInfo, bool internal, const char *relativePath)
{
    std::string realPath;
    realPath.resize(PATH_MAX);
    if (!RealPath(moduleName, realPath.data(), PATH_MAX))
    {
        errInfo = "failed to realpath module name.";
        return nullptr;
    }
    realPath.resize(strlen(realPath.c_str()));
    std::lock_guard<std::mutex> modulesLock(g_modulesMutex);
    NativeModule *cm = nullptr;
    NativeModuleHeadTailStruct cacheHeadTailNativeModule;
    NativeModule *module = FindNativeModuleByCache(realPath.c_str(), nullptr, cm, cacheHeadTailNativeModule);
    if (!module)
    {
        std::lock_guard<std::mutex> lock(g_lastModuleMutex);
        uv_lib_t *lib = new uv_lib_t;
        if (uv_dlopen(realPath.c_str(), lib))
        {
            errInfo = "failed to open lib: ";
            errInfo += moduleName;
            errInfo += ", reason: ";
            errInfo += uv_dlerror(lib);
            return nullptr;
        }
        if (g_lastModule)
        {
            g_nativeModules[realPath] = {lib, g_lastModule};
            module = g_lastModule;
        }
        else
        {
            uv_dlclose(lib);
            delete lib;
            errInfo = "Module dose not provide an entry, path: ";
            errInfo += moduleName;
            return nullptr;
        }
        g_lastModule = nullptr;
    }
    return module;
}

bool NativeModuleManager::CheckNativeListChanged(
    const NativeModule *cacheHeadNativeModule, const NativeModule *cacheTailNativeModule) { return true; }

void NativeModuleManager::SetModuleLoadChecker(const std::shared_ptr<ModuleCheckerDelegate> &moduleCheckerDelegate) {}
void NativeModuleManager::SetNativeEngine(std::string moduleKey, NativeEngine *nativeEngine) {}
LIBHANDLE NativeModuleManager::GetNativeModuleHandle(const std::string &moduleKey) const
{
    std::lock_guard<std::mutex> lock(g_modulesMutex);
    auto module = g_nativeModules.find(moduleKey);
    if (module == g_nativeModules.end())
        return nullptr;
    return reinterpret_cast<void *>(module->second.second);
}
bool NativeModuleManager::RemoveNativeModule(const std::string &moduleKey)
{
    std::lock_guard<std::mutex> lock(g_modulesMutex);
    return g_nativeModules.erase(moduleKey) > 0;
}
bool NativeModuleManager::UnloadModuleLibrary(LIBHANDLE handle)
{
    if (!handle)
        return false;
    uv_lib_t *lib = reinterpret_cast<uv_lib_t *>(handle);
    uv_dlclose(lib);
    return true;
}

bool NativeModuleManager::UnloadNativeModule(const std::string &moduleKey)
{
    auto module = GetNativeModuleHandle(moduleKey);
    if (!module)
        return false;
    if (!RemoveNativeModule(moduleKey))
        return false;
    return UnloadModuleLibrary(module);
}

std::string NativeModuleManager::GetModuleFileName(const char *moduleName, bool isAppModule) { return ""; }
